/**
 * Copyright 2008 Benjamin Possolo
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.javenue.util.process;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.LinkedList;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * An abstract class that is used to model potentially long-running tasks that can be
 * executed, stopped, persisted and later restarted.
 * <p>
 * A process is associated with a single thread of execution.
 * The thread imperatively executes the steps in a process's <code>run()</code>
 * method. Often times, a process must communicate with external components
 * or resources, for example, to receive some sort of input data or approval.
 * Wait states can be implemented using traditional <tt>Condition</tt> variables and
 * <code>await()</code>/<code>signal()</code> or using some of the other constructs
 * provided by the <tt>java.util.concurrent</tt> package such as <tt>CyclicBarrier</tt> and 
 * <tt>CountDownLatch</tt>.
 * <p>
 * The major difference between a process in this context and a JBPM-style process 
 * is that the JBPM-style process is used for defining a workflow that is driven by external threads of execution.
 * A JBPM process uses a graph-based structure to define control flow. External threads of execution send signals to
 * the process to change states. In JBPM, transactions are demarcated by the external threads of execution and usually begin before
 * a signal is sent to a process and conclude once the call to that signal has completed (which occurs when control within the
 * process has encountered a wait state). Once a wait state has been reached, the process (i.e. it's state variables) 
 * can be persisted.
 * <p>
 * In this framework's context, the process is the thread of execution and it is responsible for defining
 * transactional units of work. Units of work can include interacting with collaborators. The process's state is disjoint from its
 * owning process and is mutated within a unit of work. The owning process defines the modifications to the state. Units of work 
 * are defined within a <tt>Transition</tt> or <tt>TransitionWithResult</tt>. Outside of a transition, the process (i.e. its  
 * thread of execution) can enter wait states. A wait state usually involves a call to some method that blocks, for 
 * example <code>sleep()</code> or <code>await()</code> and generally implies that the process is waiting for information.
 * <p>
 * The entity that represents a process's state is encapsulated within a <tt>ProcessState</tt> class which is explicitly
 * associated with a <tt>Process</tt> via class generics. Since a <tt>ProcessState</tt> instance is disjoint from a <tt>Process</tt>,
 * it can be easily persisted and manipulated by its owning <tt>Process</tt> or inspected by other components.
 * <p>
 * A <tt>Process</tt> and its <tt>ProcessState</tt> are logically connected via a primary key: the identifier 
 * property <code>id</code>.
 * 
 * @see TransitionManager
 * @see Transition
 * @see TransitionWithResult
 * @see ProcessState
 * @see Runnable
 * @author Benjamin Possolo
 * <p>Created on Jul 24, 2007
 */
public abstract class Process<T extends ProcessState> implements Runnable {
	
	//-------------------------------------------------------------------------
	//Class variables
	//-------------------------------------------------------------------------
	
	/**
	 * A logger that subclasses can use.
	 */
	protected static final Logger log = LoggerFactory.getLogger(Process.class);
	
	/**
	 * The default group that a newly created process belongs to.
	 * This is the default value for the <ocde>processGroup</code> member variable.
	 */
	public static final String NO_PROCESS_GROUP = "no_process_group";
	
	/**
	 * The default termination value for a process that has not yet run
	 * or terminated. This is the default value for the <code>terminationCode</code>
	 * member variable.
	 */
	public static final int TERMINATION_CODE_NOT_SET = -3000;
	
	//-------------------------------------------------------------------------
	//Member variables
	//-------------------------------------------------------------------------
	
	/**
	 * A unique identifier for the process.
	 */
	private Integer id;
	
	/**
	 * A name for the process. Generally, this should be unique although it musn't be.
	 */
	private String name;
	
	/**
	 * The name of the process group that the process belongs to. By default the process
	 * group corresponds to the constant <code>NO_PROCESS_GROUP</code>.
	 */
	private String processGroup = NO_PROCESS_GROUP;
	
	/**
	 * Flag that indicates whether an idempotent transition should be aborted. When this flag is
	 * set to true (via the <code>abortIdempotentTransition()</code> method), no further attempts will be made
	 * to re-execute a transition.
	 */
	private boolean abortIdempotentTransition = false;
	
	/**
	 * The termination code of a process which has run to completion. Subclasses
	 * should set the termination code at the end of the <code>run()</code> method
	 * so that external components can determine the manner in which the process finished.
	 * The default termination code corresponds to the constant <code>TERMINATION_CODE_NOT_SET</code>.
	 */
	private int terminationCode = TERMINATION_CODE_NOT_SET;
	
	/**
	 * The TransitionManager that is used for executing transition code within
	 * a transaction and which automatically manages the <code>processState</code>
	 * object when the <code>stateManaged</code> flag is <code>true</code>.
	 */
	private TransitionManager transitionManager;
	
	/**
	 * A list of <tt>PropertyChangeListeners</tt> that are notified whenever the <code>progressStatus</code>
	 * property changes. <tt>PropertyChangeListener</tt>s should listen for changes to the property name:
	 * "progressStatus".
	 */
	private LinkedList<PropertyChangeListener> progressListeners = new LinkedList<PropertyChangeListener>();
	
	/**
	 * Represents the current progress status of this process. The progress status can be
	 * updated during the execution of the process so that other components can monitor the process's
	 * completion status.
	 */
	private ProgressStatus progressStatus;
	
	/**
	 * Represents the state of this process. The <code>processState</code> is an instance of
	 * <tt>ProcessState</tt> and includes the <code>state</code> and <code>previousState</code>
	 * attributes as well as any other user-defined attributes that should be persisted.
	 */
	private T processState;
	
	/**
	 * A flag variable that indicates whether the TransitionManager should automatically manage the
	 * <code>processState</code> object of this process. If this flag is true, then the
	 * <code>processState</code> is fetched from the persistence store and injected into this process
	 * before a <tt>Transition</tt> or <tt>TransitionWithResult</tt> begins, and then persisted back
	 * to the persistence store once the transition completes successfully.
	 */
	private boolean stateManaged = true;
	
	/**
	 * List of <code>ProcessTerminationListener</code>s that should be called-back when the
	 * process ends.
	 */
	private LinkedList<ProcessTerminationListener> processTerminationListeners 
		= new LinkedList<ProcessTerminationListener>();
	
	/**
	 * Lock used to synchronize access to the <code>stopCondition</code>
	 * condition variable and to the <code>abortIdempotentTransition</code> flag.
	 */
	private ReentrantLock failedTransitionLock = new ReentrantLock();
	
	/**
	 * Condition variable used by the thread executing this process to perform timed
	 * waits between failed transition execution attempts. Another thread can call
	 * <code>abortIdempotentTransition()</code> (usually implicitly via the <code>stop()</code>
	 * method), to signal the thread executing this process that it should abort
	 * any futher attempts to execute the transition.
	 */
	private Condition stopCondition = failedTransitionLock.newCondition();
	
	//-------------------------------------------------------------------------
	//Public methods
	//-------------------------------------------------------------------------
	
	/**
	 * Gets the unique identifier.
	 * 
	 * @return the process's unique identifier, or null if it has not yet been set
	 */
	public Integer getId() {
		return id;
	}

	/**
	 * Sets the unique identifier.
	 * 
	 * @param id the identifier value to set
	 */
	public void setId(Integer id) {
		this.id = id;
	}
	
	/**
	 * Gets the process name.
	 * 
	 * @return the process's name, or null if it has not yet been set
	 */
	public String getName() {
		return name;
	}
	
	/**
	 * Sets the name for the process. Generally, the name should be unique
	 * although it musn't be.
	 * 
	 * @param name the name to set
	 */
	public void setName(String name) {
		this.name = name;
	}
	
	/**
	 * Sets the process group that this process should belong to.
	 * The process group simply functions as a way to logically seperate processes.
	 * 
	 * @param processGroup the name of a group that the process belongs to
	 */
	public void setProcessGroup(String processGroup){
		this.processGroup = processGroup;
	}
	
	/**
	 * Gets the progress status of this process. The progress status can represent
	 * percentage completion of a process, for example on a scale of 0 - 100.
	 * 
	 * @return the progress status object
	 */
	public ProgressStatus getProgressStatus() {
		return progressStatus;
	}
	
	/**
	 * Gets the <code>stateManaged</code> flag.
	 * <p>
	 * The <code>stateManaged</code> flag variable represents whether the
	 * <tt>TransitionManager</tt> should automatically load and persist the <code>processsState</code>
	 * object before and after transitions are executed, respectively.
	 * 
	 * @return the boolean value of the <code>stateManaged</code> flag
	 */
	public boolean isStateManaged() {
		return stateManaged;
	}
	
	/**
	 * Sets the <code>stateManaged</code> flag.
	 * <p>
	 * This flag is <code>true</code> by default
	 * and should only be set to <code>false</code> if the <code>processState</code> object
	 * should not be managed (i.e. automatically loaded and persisted) by the <tt>TransitionManager</tt>
	 * when performing transitions.
	 * 
	 * @param stateManaged the boolean flag value
	 */
	public void setStateManaged(boolean stateManaged) {
		this.stateManaged = stateManaged;
	}
	
	/**
	 * Sets the transition manager. The transition manager is required
	 * to execute transitions in a transactional fashion and to manage the process
	 * state object.
	 * 
	 * @param transitionManager the transition manager to set
	 */
	public void setTransitionManager(TransitionManager transitionManager) {
		this.transitionManager = transitionManager;
	}
	
	/**
	 * Gets the name of the process group that this thread belongs to.
	 * If the process group has not been explicitly set, then the default
	 * process group represented by the constant <code>NO_PROCESS_GROUP</code>
	 * is returned.
	 * 
	 * @return the <code>processGroup</code> or <code>NO_PROCESS_GROUP</code> if none has been explicitly set
	 */
	public String getProcessGroup(){
		return processGroup;
	}
	
	/**
	 * Gets the termination code. It only makes sense to call this method
	 * once the process has executed and terminated. If this process has not explicitly
	 * set the termination code, then the constant <code>TERMINATION_CODE_NOT_SET</code> is returned.
	 * 
	 * @return the termination code
	 */
	public int getTerminationCode() {
		return terminationCode;
	}
	
	/**
	 * Gets a string representation of the termination code.
	 * 
	 * @return the termination code as a string 
	 */
	public String getTerminationCodeAsString(){
		return getTerminationCodeAsString(getTerminationCode());
	}
	
	/**
	 * Adds a <tt>PropertyChangeListener</tt> to the list of listeners that are called-back
	 * when the progress status changes.
	 * <p>
	 * This method is thread-safe.
	 * 
	 * @param progressListener the <tt>PropertyChangeListener</tt> that is to be 
	 * called-back when the progess status changes
	 */
	public void addProgressListener(PropertyChangeListener progressListener){
		synchronized(progressListeners){
			progressListeners.add(progressListener);
		}
	}
	
	/**
	 * Removes a <tt>PropertyChangeListener</tt> from the list of listeners that are called-back
	 * when the progress status changes.
	 * <p>
	 * This method is thread-safe.
	 * 
	 * @param progressListener the <tt>PropertyChangeListener</tt> to remove from the list of listeners
	 */
	public void removeProgressListener(PropertyChangeListener progressListener){
		synchronized(progressListener){
			progressListeners.remove(progressListener);
		}
	}
	
	/**
	 * Adds a <tt>ProcessTerminationListener</tt> to the list of listeners that are called-back
	 * when the process finishes executing its run method.
	 * <p>
	 * This method is thread-safe.
	 * 
	 * @param listener the process termination listener that is to be called-back once the process terminates
	 */
	public void addProcessTerminationListener(ProcessTerminationListener listener){
		synchronized(processTerminationListeners){
			processTerminationListeners.add(listener);
		}
	}
	
	/**
	 * Removes a <tt>ProcessTerminationListener</tt> from the list of listeners that are called-back
	 * when the process finishes executing its run method.
	 * <p>
	 * This method is thread-safe.
	 * 
	 * @param listener the process termination listener to remove from the list of listeners
	 */
	public void removeProcessTerminationListener(ProcessTerminationListener listener){
		synchronized(processTerminationListeners){
			processTerminationListeners.remove(listener);
		}
	}
	
	/**
	 * Convenience method for retrieving the <code>processState.state</code> property.
	 * <p>
	 * This method simply calls <code>getState()</code> within a <tt>TransitionWithResult</tt>.
	 * <p>
	 * A <tt>Process</tt> should <i>not</i> invoke this method from within a <tt>Transition</tt> 
	 * or <tt>TransitionWithResult</tt>. The convenience method <code>getState()</code> 
	 * can be used to retrieve the state value from within
	 * a <tt>Transition</tt> or <tt>TransitionWithResult</tt>.
	 * <p>
	 * This method is public so that external components can determine the state of a <tt>Process</tt>.
	 * <p>
	 * Note: this method will load the <tt>Process</tt>'s <tt>ProcessState</tt> object from the persistence
	 * store. This method is guaranteed to execute serially with respect to any other transitions that the
	 * <tt>Process</tt> may attempt to perform via the <tt>TransitionManager</tt>.
	 * <p>
	 * This method makes only one attempt to load the state. It simply invokes the <code>loadState(int, String)</code>
	 * variation of this method with <code>1</code> and <code>"0s"</code> as parameters.
	 * 
	 * @return the current state of the process
	 * @throws ProcessStateManagementException if the <code>stateManaged</code> flag is true and an error 
	 * occurs loading the <code>processState</code> from the persistence store
	 * @throws IllegalStateException if the <code>stateManaged</code> flag is <code>false</code>
	 */
	public int loadState() throws ProcessStateManagementException {
		
		return loadState(1, "0s");
	}
	
	
	/**
	 * Convenience method for retrieving the <code>processState.state</code> property.
	 * <p>
	 * This method simply calls <code>getState()</code> within a <tt>TransitionWithResult</tt>.
	 * <p>
	 * A <tt>Process</tt> should <i>not</i> invoke this method from within a <tt>Transition</tt> 
	 * or <tt>TransitionWithResult</tt>. The convenience method <code>getState()</code> 
	 * can be used to retrieve the state value from within
	 * a <tt>Transition</tt> or <tt>TransitionWithResult</tt>.
	 * <p>
	 * This method is public so that external components can determine the state of a <tt>Process</tt>.
	 * <p>
	 * Note: this method will load the <tt>Process</tt>'s <tt>ProcessState</tt> object from the persistence
	 * store. This method is guaranteed to execute serially with respect to any other transitions that the
	 * <tt>Process</tt> may attempt to perform via the <tt>TransitionManager</tt>.
	 * <p>
	 * If a <tt>ProcessStateManagementException</tt> occurs while loading the process state, then this method
	 * will retry to load the state up to <code>maxAttempts</code> times with a delay of <code>delay</code> between
	 * retries. In other words, the <tt>TransitionWithResult</tt> is invoked in an idempotent fashion.
	 * 
	 * @param maxAttempts maximum number of times to retry loading the state
	 * @param delay a string that represents the amount of time to wait between 
	 * retries (ex. <code>2s</code> = two seconds, <code>10m</code> = ten minutes, 
	 * <code>5h</code> = five hours), null if no delay is desired
	 * @return the current state of the process
	 * @throws ProcessStateManagementException if the <code>stateManaged</code> flag is true and an error 
	 * occurs loading the <code>processState</code> from the persistence store
	 * @throws IllegalStateException if the <code>stateManaged</code> flag is <code>false</code>
	 */
	public int loadState(int maxAttempts, String delay){
		
		if( ! stateManaged )
			throw new IllegalStateException("The state cannot be loaded since it is not automatically managed");
		
		if( log.isDebugEnabled() )
			log.debug("Loading process state for ["
				+ getName() + "] with ID [" + getId() + "]");
		
		return ((Integer)performTransition(new TransitionWithResult(){
			public Object doTransition(){
				return new Integer(getState());
			}
		}, maxAttempts, delay, new Class[0])).intValue();
	}
	
	//-------------------------------------------------------------------------
	//Abstract methods
	//-------------------------------------------------------------------------
	
	/**
	 * Abstract method should be overridden by subclasses in order to allow external components
	 * to issue a stop request to an asynchronously executing process. If a process implementation
	 * involves <tt>@Idempotent</tt> transitions, then one of the first things the <code>stop()</code> method 
	 * should do is invoke <code>abortIdempotentTransition()</code> in order to prevent the subsystem
	 * from reattempting a failed transition.
	 * <p>
	 * Note: if this process can potentially block on some business operation, it may be necessary to
	 * interrupt the thread executing this process in addition to invoking the <code>stop()</code> method.
	 * If this is the case, one should design the process and <code>stop()</code> method such that the
	 * <code>stop()</code> method is invoked first, and then an interrupt is issued which wakes up the blocked
	 * thread so that it exits the <code>run()</code> method.
	 * <p>
	 * The service that was used to execute this process asynchronously should offer some method for
	 * interrupting the executing thread. The <tt>FutureTask</tt> and <tt>ProcessManager</tt> interfaces
	 * both offer methods for interrupting an asynchronous process.
	 * <p>
	 */
	public abstract void stop();
	
	/**
	 * Abstract method should be overridden by subclasses in order to allow external components
	 * to retrieve a descriptive string about the termination code of the process.
	 * <p>
	 * Primarily of use for logging.
	 * 
	 * @param terminationCode the termination code to convert to a string
	 * @return a user-understandable string representation of the termination code
	 */
	public abstract String getTerminationCodeAsString(int terminationCode);
	
	//-------------------------------------------------------------------------
	//Protected methods
	//-------------------------------------------------------------------------
	
	/**
	 * Executes a <tt>Transition</tt> in an atomic/transactional fashion.
	 * <p>
	 * The <tt>Transition</tt> is executed by the <tt>TransitionManager</tt> which ensures that a transaction is automatically started
	 * before the <tt>Transition</tt>'s <code>doTransition()</code> method is invoked. If an exception is thrown from
	 * within the <code>doTransition()</code> method, then the transaction is rolled-back, otherwise it its committed.
	 * <p>
	 * The <tt>ProcessState</tt> object that corresponds to this process is automatically injected into this process before the
	 * transition is executed. If the tansition executes successfully, then the <tt>ProcessState</tt> object is
	 * automatically persisted upon completion. Note: this behaviour can be disabled by setting the <code>stateManaged</code> flag to
	 * <code>false</code>.
	 * <p>
	 * If the <tt>Transition</tt> has been annotated <tt>@Idempotent</tt>, then the transition may be retried multiple
	 * times if it fails. If an attempt to execute the transition fails (i.e. it throws an exception
	 * that is an instance of a class specified in the <code>ex</code> array of the <tt>@Idempotent</tt> annotation)
	 * then it will wait for an amount of time specified by the annotation's <code>delay</code> argument and then
	 * the transition will be attempted again. The maximum number of attempts is specified by the
	 * annotation's <code>maxAttempts</code> argument.
	 * <p>
	 * If another thread wishes to stop this process while an idempotent transition is executing or within
	 * a sequence of retry attempts, then the <code>stop()</code> implementation should include a call to
	 * <code>abortIdempotentTransition()</code> in order to notify this method that it should not perform
	 * any more retries of a failed transition.
	 * <p>
	 * If a transition has failed and the thread executing the process
	 * is waiting to retry the transition when <code>abortIdempotentTransition()</code> is called, then this
	 * method will immediately throw the exception that caused the last transition attempt to fail,
	 * wrapped in a <tt>TransitionException</tt>.
	 * <p>
	 * If the thread executing this process is interrupted while it is waiting to reattempt a failed
	 * transition, then this method will throw the exception that caused the last transition attempt to fail
	 * (wrapped in a <tt>TransitionException</tt>) and will reset the thread's <code>interrupted</code>
	 * status back to true.
	 * 
	 * @param transition the <tt>Transition</tt> to execute
	 * @throws TransitionException if an error occurs while executing the transition. If the transition has been marked 
	 * <tt>@Idempotent</tt>, then the <tt>TransitionException</tt> will only be thrown after <code>maxAttempts</code> failed tries
	 * @throws ProcessStateManagementException if the <code>stateManaged</code> flag is true and an error 
	 * occurs loading or persisting the <code>processState</code>
	 */
	protected void performTransition(Transition transition) throws TransitionException, ProcessStateManagementException {
		
		performIdempotentTransition(transition);
	}
	
	/**
	 * Executes a <tt>TransitionWithResult</tt> in an atomic/transactional fashion.
	 * <p>
	 * The <tt>TransitionWithResult</tt> is executed by the <tt>TransitionManager</tt> which ensures that a transaction is automatically started
	 * before the <tt>TransitionWithResult</tt>'s <code>doTransition()</code> method is invoked. If an exception is thrown from
	 * within the <code>doTransition()</code> method, then the transaction is rolled-back, otherwise it its committed.
	 * <p>
	 * The <tt>ProcessState</tt> object that corresponds to this process is automatically injected into this process before the
	 * transition is executed. If the tansition executes successfully, then the <tt>ProcessState</tt> object is
	 * automatically persisted upon completion. Note: this behaviour can be disabled by setting the <code>stateManaged</code> flag to
	 * <code>false</code>.
	 * <p>
	 * If the <tt>TransitionWithResult</tt> has been annotated <tt>@Idempotent</tt>, then the transition may be retried multiple
	 * times if it fails. If an attempt to execute the transition fails (i.e. it throws an exception
	 * that is an instance of a class specified in the <code>ex</code> array of the <tt>@Idempotent</tt> annotation)
	 * then it will wait for an amount of time specified by the annotation's <code>delay</code> argument and then
	 * the transition will be attempted again. The maximum number of attempts is specified by the
	 * annotation's <code>maxAttempts</code> argument.
	 * <p>
	 * If another thread wishes to stop this process while an idempotent transition is executing or within
	 * a sequence of retry attempts, then the <code>stop()</code> implementation should include a call to
	 * <code>abortIdempotentTransition()</code> in order to notify this method that it should not perform
	 * any more retries of a failed transition.
	 * <p>
	 * If a transition has failed and the thread executing the process
	 * is waiting to retry the transition when <code>abortIdempotentTransition()</code> is called, then this
	 * method will immediately throw the exception that caused the last transition attempt to fail,
	 * wrapped in a <tt>TransitionException</tt>.
	 * <p>
	 * If the thread executing this process is interrupted while it is waiting to reattempt a failed
	 * transition, then this method will throw the exception that caused the last transition attempt to fail
	 * (wrapped in a <tt>TransitionException</tt>) and will reset the thread's <code>interrupted</code>
	 * status back to true.
	 * 
	 * @param transition the <tt>TransitionWithResult</tt> to execute
	 * @return the result of the <tt>TransitionWithResult</tt>'s <code>doTransition()</code> method
	 * @throws TransitionException if an error occurs while executing the transition. If the transition has been marked
	 * <tt>@Idempotent</tt>, then the <tt>TransitionException</tt> will only be thrown after <code>maxAttempts</code> failed tries
	 * @throws ProcessStateManagementException if the <code>stateManaged</code> flag is true and an error 
	 * occurs loading or persisting the <code>processState</code>
	 */
	protected Object performTransition(TransitionWithResult transition) throws TransitionException, ProcessStateManagementException {
		
		return performIdempotentTransition(transition);
	}
	
	/**
	 * Executes a <tt>Transition</tt> in an atomic/transactional and idempotent fashion.
	 * <p>
	 * The <tt>Transition</tt> is executed by the <tt>TransitionManager</tt> which ensures that a transaction is automatically started
	 * before the <tt>Transition</tt>'s <code>doTransition()</code> method is invoked. If an exception is thrown from
	 * within the <code>doTransition()</code> method, then the transaction is rolled-back, otherwise it its committed.
	 * <p>
	 * The <tt>ProcessState</tt> object that corresponds to this process is automatically injected into this process before the
	 * transition is executed. If the tansition executes successfully, then the <tt>ProcessState</tt> object is
	 * automatically persisted upon completion. Note: this behaviour can be disabled by setting the <code>stateManaged</code> flag to
	 * <code>false</code>.
	 * <p>
	 * If an attempt to execute the transition fails (i.e. it throws an exception
	 * that is an instance of a class specified in the <code>ex</code> array)
	 * then it will wait for an amount of time specified by the <code>delay</code> argument and then
	 * the transition will be attempted again. The maximum number of attempts is specified by the
	 * <code>maxAttempts</code> argument.
	 * <p>
	 * If another thread wishes to stop this process while an idempotent transition is executing or within
	 * a sequence of retry attempts, then the <code>stop()</code> implementation should include a call to
	 * <code>abortIdempotentTransition()</code> in order to notify this method that it should not perform
	 * any more retries of a failed transition.
	 * <p>
	 * If a transition has failed and the thread executing the process
	 * is waiting to retry the transition when <code>abortIdempotentTransition()</code> is called, then this
	 * method will immediately throw the exception that caused the last transition attempt to fail,
	 * wrapped in a <tt>TransitionException</tt>.
	 * <p>
	 * If the thread executing this process is interrupted while it is waiting to reattempt a failed
	 * transition, then this method will throw the exception that caused the last transition attempt to fail
	 * (wrapped in a <tt>TransitionException</tt>) and will reset the thread's <code>interrupted</code>
	 * status back to true.
	 * <p>
	 * If the <tt>Transition</tt> has been annotated <tt>@Idempotent</tt>, then this method's parameters
	 * will override the paramters in the <tt>@Idempotent</tt> annotation.
	 * 
	 * @param transition the <tt>Transition</tt> to execute
	 * @param maxAttempts maximum number of times to retry the transition
	 * @param delay the amount of time to wait between retries as a string postfixed with a time unit (either "s", "m" or "h")
	 * @param ex an array of exception classes that cause the transition to be retried, if array is empty, then all exceptions cause a retry
	 * @throws TransitionException if an error occurs while executing the transition. Then
	 * the <tt>TransitionException</tt> will only be thrown after <code>maxAttempts</code> failed tries
	 * @throws ProcessStateManagementException if the <code>stateManaged</code> flag is true and an error 
	 * occurs loading or persisting the <code>processState</code>
	 */
	protected void performTransition(Transition transition, int maxAttempts, String delay, Class<?>[] ex)
		throws TransitionException, ProcessStateManagementException {
		
		performIdempotentTransition(transition, maxAttempts, delay, ex);
	}
	
	/**
	 * Executes a <tt>TransitionWithResult</tt> in an atomic/transactional and idempotent fashion.
	 * <p>
	 * The <tt>TransitionWithResult</tt> is executed by the <tt>TransitionManager</tt> which ensures that a transaction is automatically started
	 * before the <tt>Transition</tt>'s <code>doTransition()</code> method is invoked. If an exception is thrown from
	 * within the <code>doTransition()</code> method, then the transaction is rolled-back, otherwise it its committed.
	 * <p>
	 * The <tt>ProcessState</tt> object that corresponds to this process is automatically injected into this process before the
	 * transition is executed. If the tansition executes successfully, then the <tt>ProcessState</tt> object is
	 * automatically persisted upon completion. Note: this behaviour can be disabled by setting the <code>stateManaged</code> flag to
	 * <code>false</code>.
	 * <p>
	 * If an attempt to execute the transition fails (i.e. it throws an exception
	 * that is an instance of a class specified in the <code>ex</code> array)
	 * then it will wait for an amount of time specified by the <code>delay</code> argument and then
	 * the transition will be attempted again. The maximum number of attempts is specified by the
	 * <code>maxAttempts</code> argument.
	 * <p>
	 * If another thread wishes to stop this process while an idempotent transition is executing or within
	 * a sequence of retry attempts, then the <code>stop()</code> implementation should include a call to
	 * <code>abortIdempotentTransition()</code> in order to notify this method that it should not perform
	 * any more retries of a failed transition.
	 * <p>
	 * If a transition has failed and the thread executing the process
	 * is waiting to retry the transition when <code>abortIdempotentTransition()</code> is called, then this
	 * method will immediately throw the exception that caused the last transition attempt to fail,
	 * wrapped in a <tt>TransitionException</tt>.
	 * <p>
	 * If the thread executing this process is interrupted while it is waiting to reattempt a failed
	 * transition, then this method will throw the exception that caused the last transition attempt to fail
	 * (wrapped in a <tt>TransitionException</tt>) and will reset the thread's <code>interrupted</code>
	 * status back to true.
	 * <p>
	 * If the <tt>TransitionWithResult</tt> has been annotated <tt>@Idempotent</tt>, then this method's parameters
	 * will override the paramters in the <tt>@Idempotent</tt> annotation.
	 * 
	 * @param transition the <tt>TransitionWithResult</tt> to execute
	 * @param maxAttempts maximum number of times to retry the transition
	 * @param delay the amount of time to wait between retries as a string postfixed with a time unit (either "s", "m" or "h")
	 * @param ex an array of exception classes that cause the transition to be retried, if array is empty, then all exceptions cause a retry
	 * @return the result of the <tt>TransitionWithResult</tt>'s <code>doTransition()</code> method
	 * @throws TransitionException if an error occurs while executing the transition. Then
	 * the <tt>TransitionException</tt> will only be thrown after <code>maxAttempts</code> failed tries
	 * @throws ProcessStateManagementException if the <code>stateManaged</code> flag is true and an error 
	 * occurs loading or persisting the <code>processState</code>
	 */
	protected Object performTransition(TransitionWithResult transition, int maxAttempts, String delay, Class<?>[] ex)
		throws TransitionException, ProcessStateManagementException {
		
		return performIdempotentTransition(transition, maxAttempts, delay, ex);
	}
	
	
	/**
	 * Prevents failed idempotent transitions from being retried.
	 * <p>
	 * <ul>
	 * <li>If this method is called after a transition has begun executing and which later fails, then the
	 * thread executing the transition will not attempt to retry the transition and will immediately throw the
	 * TransitionException which caused it to fail.</li>
	 * <li>If this method is called before an idempotent transition begins executing,
	 * then the transition will be attempted exactly once, regardless of whether it is idempotent or not.</li>
	 * <li>If this method is invoked while the thread executing the process is waiting to retry a failed
	 * idempotent transition, then the thread will be awoken and it will immediately throw the <tt>TransitionException</tt>
	 * that caused the last attempt to fail.</li>
	 * </ul>
	 * <p>
	 * This method should be invoked from within the <code>stop()</code> method if this process executes any idempotent
	 * transitions. If the process's lifespan is modeled around polling a boolean <code>done</code> flag of some sort, then
	 * it would make sense for the <code>stop()</code> implementation to to set that flag before invoking this method in order
	 * to improve response time to a <code>stop()</code> request.
	 * 
	 * Note: Once this method has been called, there is no way to 
	 * set the <code>abortIdempotentTransition</code> flag back to false. It is 
	 * assumed that this process instance will be disposed of once this method is invoked
	 * and the process has terminated.
	 */
	protected void abortIdempotentTransition(){
		
		failedTransitionLock.lock();
		try{
			abortIdempotentTransition = true;
			stopCondition.signal();
		}
		finally{
			failedTransitionLock.unlock();
		}
	}
	
	/**
	 * Convenience method for getting the <code>processState.state</code> property.
	 * <p>
	 * In order to ensure that the state value is fresh, this method <i>must</i> be executed within
	 * a <tt>Transition</tt> or <tt>TransitionWithResult</tt>.
	 * <p>
	 * In order to get the state value outside of a <tt>Transition</tt> or <tt>TransitionWithResult</tt>,
	 * use the <code>loadState()</code> method.
	 * 
	 * @return the current state of the process
	 * @throws IllegalStateException if the processState property has not yet been set
	 */
	protected int getState() {
		
		if( getProcessState() == null )
			throw new IllegalStateException("processState property has not been set");
		
		return getProcessState().getState();
	}
	
	/**
	 * Convenience method for setting the <code>processState.state</code> property.
	 * <p>
	 * It is logically incorrect to attempt to set the state value before having
	 * loaded the process state object (which occurs implicitly before executing a <tt>Transition</tt>
	 *  or <tt>TransitionWithResult</tt>) when the 
	 * <code>stateManaged</code> flag is <code>true</code>.
	 * <p>
	 * This method should only be invoked from within a <tt>Transition</tt> or <tt>TransitionWithResult</tt> callback.
	 * 
	 * @param state the value to set as the process's current state
	 * @throws IllegalStateException if the <code>processState</code> property has not been set yet.
	 */
	protected void setState(int state){
		
		if( processState == null )
			throw new IllegalStateException("processState property has not been set");
		
		processState.setState(state);
	}
	
	/**
	 * Gets the process state object.
	 * 
	 * @return the processState object
	 */
	protected T getProcessState() {
		return processState;
	}
	
	/**
	 * Sets the process state object.
	 * <p>
	 * When the <code>stateManaged</code> flag is true, the process state object will
	 * be injected into this process by the <tt>TransitionManager</tt> before any attempt
	 * to execute a transition occurs.
	 * <p>
	 * Note: if a custom <tt>TransitionManager</tt> implementation is defined outside of the 
	 * <code>org.javenue.util.process</code> package, then it should invoke this method using
	 * reflection. It would be unsafe to mark this method as public simply to allow other 
	 * <tt>TransitionManager</tt> implementations to inject the process state.
	 * 
	 * @param processState the process state to set
	 */
	protected void setProcessState(T processState){
		this.processState = processState;
	}
	
	/**
	 * Sets the <code>progressStatus</code>.
	 * <p>
	 * Once the new <code>progressStatus</code> is set, all <tt>PropertyChangeListeners</tt> in the
	 * <code>progressListeners</code> list are notified via a <tt>PropertyChangeEvent</tt>.
	 * <p>
	 * This method is thread-safe and one need not worry about concurrent modification attempts
	 * to the <code>progressListeners</code> list.
	 * 
	 * @param progressStatus the new progress status to set
	 */
	protected void setProgressStatus(ProgressStatus progressStatus) {
		
		ProgressStatus prev = this.progressStatus;
		this.progressStatus = progressStatus;
		
		PropertyChangeListener[] listeners;
		
		synchronized(progressListeners){
			listeners = progressListeners.toArray(new PropertyChangeListener[progressListeners.size()]);
		}
		
		PropertyChangeEvent event = new PropertyChangeEvent(this, "progressStatus", prev, progressStatus);
		
		for( PropertyChangeListener listener : listeners )
			listener.propertyChange(event);
	}
	
	/**
	 * Convenience method sets the <code>progressStatus</code> to the given progress and message values.
	 * <p>
	 * Simply creates a new <tt>ProgressStatus</tt> based on the two arguments and invokes
	 * the <code>setProgressStatus(ProgressStatus progressStatus)</code> method.
	 * <p>
	 * Once the new <code>progressStatus</code> is set, all <tt>PropertyChangeListeners</tt> in the
	 * <code>progressListeners</code> list are notified via a <tt>PropertyChangeEvent</tt>.
	 * <p>
	 * This method is thread-safe and one need not worry about concurrent modification attempts
	 * to the <code>progressListeners</code> list.
	 * 
	 * @param progress the new progress value to set
	 * @param message a descriptive message that describes the new progress value
	 */
	protected void setProgressStatus(int progress, String message){
		setProgressStatus(new ProgressStatus(progress, message));
	}
	
	/**
	 * Sets the termination code of the process. This method should be invoked
	 * before the <code>run()</code> method completes so that other components
	 * can determine how the process finished.
	 * <p>
	 * This method is protected since it only makes sense for a subclass itself 
	 * to define the termination code.
	 * 
	 * @param terminationCode the termination code to set
	 */
	protected void setTerminationCode(int terminationCode) {
		
		if( log.isDebugEnabled() )
			log.debug(getName() + " set termination code to [" 
				+ getTerminationCodeAsString(terminationCode) + "]");
		
		this.terminationCode = terminationCode;
	}
	 
	/**
	 * Notifies all <tt>ProcessTerminationListener</tt>s that the process has finished.
	 * <p>
	 * This method iterates over all process termination listeners and invokes their
	 * <code>processTerminated(Process process)</code> method. Normally, this method will be
	 * invoked by a <tt>Process</tt> subclass at the end of the <code>run()</code> method
	 * within a <code>finally</code> block.
	 * <p>
	 * This method is thread-safe.
	 */
	protected void notifyProcessTerminationListeners(){
		
		ProcessTerminationListener[] listeners;
		
		synchronized(processTerminationListeners){
			listeners = processTerminationListeners
				.toArray(new ProcessTerminationListener[processTerminationListeners.size()]);
		}
		
		for( ProcessTerminationListener listener : listeners )
			listener.processTerminated(this);
	}
	
	//-------------------------------------------------------------------------
	//Private methods
	//-------------------------------------------------------------------------
	
	/**
	 * Attempts to execute a <tt>Transition</tt> or <tt>TransitionWithResult</tt> in an idempotent fashion.
	 * <p>
	 * This method simply delegates to the various <code>TransitionManager.execute()</code> methods.
	 * If an attempt to execute a transition object fails (i.e. it throws an exception
	 * that is an instance of a class specified in the <code>ex</code> array)
	 * then it will wait for an amount of time specified by the <code>delay</code> argument and then
	 * the transition will be attempted again. The maximum number of attempts is specified by the
	 * <code>maxAttempts</code> argument.
	 * <p>
	 * If another thread wishes to stop this process while an idempotent transition is executing or within
	 * a sequence of retry attempts, then the <code>stop()</code> implementation should include a call to
	 * <code>abortIdempotentTransition()</code> in order to notify this method that it should not perform
	 * any more retries of a failed transition.
	 * <p>
	 * If a transition has failed and the thread executing the process
	 * is waiting to retry the transition when <code>abortIdempotentTransition()</code> is called, then this
	 * method will immediately throw the exception that caused the last transition attempt to fail,
	 * wrapped in a <tt>TransitionException</tt>.
	 * <p>
	 * If the thread executing this process is interrupted while it is waiting to reattempt a failed
	 * transition, then this method will throw the exception that caused the last transition attempt to fail
	 * (wrapped in a <tt>TransitionException</tt>) and will reset the thread's <code>interrupted</code>
	 * status back to true.
	 * <p>
	 * This method is synchronized in order to ensure that transitions occur atomically. 
	 * 
	 * @param transitionObject an instance of either <tt>Transition</tt> or <tt>TransitionWithResult</tt>
	 * @param maxAttempts maximum number of times to retry the transition
	 * @param delay a string that represents the amount of time to wait between 
	 * retries (ex. <code>2s</code> = two seconds, <code>10m</code> = ten minutes, 
	 * <code>5h</code> = five hours), null if no delay is desired
	 * @param ex an array of exception classes that cause the transition to be retried, if array is empty, then all exceptions cause a retry
	 * @return null if the transitionObject is a <tt>Transition</tt>, otherwise the return value of the <tt>TransitionWithResult</tt> is returned
	 * @throws IllegalArgumentException, if <code>maxAttempts</code> is less than one, if the <code>delay</code>
	 * string is improperly formatted or if the <code>ex</code> array or any of its elements are null
	 * @throws TransitionException if an error occurs while executing the transition. If the transition has been marked <tt>@Idempotent</tt>, then
	 * the <tt>TransitionException</tt> will only be thrown after <code>maxAttempts</code> failed tries
	 * @throws ProcessStateManagementException if the <code>stateManaged</code> flag is true and an error 
	 * occurs loading or persisting the <code>processState</code>
	 */
	private synchronized Object performIdempotentTransition(Object transitionObject, int maxAttempts, String delay, Class<?>[] ex)
	 	throws TransitionException, ProcessStateManagementException {
		
		validateMaxAttempts(maxAttempts);
		validateExceptionsThatCauseRetryArray(ex);
		long delayTime = getDelay(delay); //converts delay string to seconds, validates delay argument as well
		
		Object result = null;

		for( int attempt = 1; attempt <= maxAttempts; attempt++ ){
			
			try{
				if( log.isDebugEnabled() )
					log.debug("Executing transition, try number: " + attempt);
				
				result = executeTransitionWithTransitionManager(transitionObject);
				break; //break out of the loop as soon as the transition completes successfully
			}
			catch(TransitionException e){
				
				log.warn("An error occurred while performing transition: " + e.getCause().getMessage());
				
				failedTransitionLock.lock();
				try{
					if( ! isRetriableException(ex, e.getCause()) || attempt == maxAttempts )
						throw e;
					
					else if( abortIdempotentTransition 
						|| stopCondition.await(delayTime, TimeUnit.SECONDS) ) //await returns true if stopCondition is signalled while waiting
						throw new TransitionException("Idempotent transition aborted while waiting to retry operation. " + e.getCause().getMessage(), e.getCause());
				}
				catch(InterruptedException ie){
					Thread.currentThread().interrupt();
					throw new TransitionException("Interrupted while waiting to retry Idempotent transition. " + e.getCause().getMessage(), e.getCause());
				}
				finally{
					failedTransitionLock.unlock();
				}
			}
			catch(ProcessStateManagementException e){
				
				log.warn("An error occurred managing the process state: " + e.getMessage());
				
				failedTransitionLock.lock();
				try{
					if( ! isRetriableException(ex, e) || attempt == maxAttempts )
						throw e;
					
					else if( abortIdempotentTransition 
						|| stopCondition.await(delayTime, TimeUnit.SECONDS) ) //await returns true if stopCondition is signalled while waiting
						throw new ProcessStateManagementException("Idempotent transition aborted while waiting to retry operation. " + e.getMessage(), e.getCause());
				}
				catch(InterruptedException ie){
					Thread.currentThread().interrupt();
					throw new ProcessStateManagementException("Interrupted while waiting to retry Idempotent transition. " + e.getMessage(), e.getCause());
				}
				finally{
					failedTransitionLock.unlock();
				}
			}
		}
		return result;
	}
	
	/**
	 * Executes the <tt>Transition</tt> or <tt>TransitionWithResult</tt> on the <tt>TransitionManager</tt>.
	 * 
	 * @param transitionObject the <tt>Transition</tt> or <tt>TransitionWithResult</tt> instance to execute.
	 * @return the result of the <tt>TransitionWithResult</tt>, or null if the <code>transitionObject</code> is 
	 * an instance of <tt>Transition</tt>
	 * @throws TransitionException if an error occurs while executing the transition
	 * @throws ProcessStateManagementException if the <code>stateManaged</code> flag is <code>true</code> and
	 * an error occurs retrieving or persisting the <tt>ProcessState</tt> object
	 */
	private Object executeTransitionWithTransitionManager(Object transitionObject){
		
		if( transitionObject instanceof Transition ){
			
			Transition transition = (Transition)transitionObject;
			
			if( stateManaged )
				transitionManager.execute(this, transition);
			else
				transitionManager.executeWithoutStateManagement(transition);
			
			return null;
		}
		else{ //transitionObject instanceof TransitionWithResult
			
			TransitionWithResult transition = (TransitionWithResult)transitionObject;
			
			if( stateManaged )
				return transitionManager.execute(this, transition);
			else
				return transitionManager.executeWithoutStateManagement(transition);
		}
	}
	
	
	/**
	 * Executes a transition using the idempotent properties specified in the transition class'
	 * <tt>@Idempotent</tt> annotation or values if the transition class is not annotated <tt>@Idempotent</tt>.
	 * <p>
	 * This method simply delegates to the <code>performIdempotentTransition(Object transitionObject, int maxAttempts, String delay, Class[] ex)</code>
	 * method.
	 * <p>
	 * If the transition class is not annotated <tt>@Idempotent</tt>, then <code>maxAttempts</code> is 1, 
	 * <code>delay</code> is 0 and <code>ex</code> is simply an empty array.
	 * <p>
	 * The <code>performTransition()</code> methods all simply delegate to this method.
	 * 
	 * @param transitionObject a <tt>Transition</tt> or <tt>TransitionWithResult</tt> object
	 * @return null if the <code>transitionObject</code> is of type <tt>Transition</tt> otherwise the return value of the <tt>TransitionWithResult</tt>
	 * @throws TransitionException if an error occurs while executing the transition, if the transition has been marked <tt>@Idempotent</tt>, then
	 * the <tt>TransitionException</tt> will only be thrown after <code>maxAttempts</code> failed tries
	 * @throws ProcessStateManagementException if the <code>stateManaged</code> flag is true and an error 
	 * occurs loading or persisting the <code>processState</code>
	 */
	private Object performIdempotentTransition(Object transitionObject)
		throws TransitionException, ProcessStateManagementException {
		
		//ex will be empty array if transitionObject is not @Idempotent or if it is @Idempotent and all exceptions cause retry
		Class<?>[] ex = getExceptionsThatCauseRetry(transitionObject);
		
		int maxAttempts = getMaxRetryAttempts(transitionObject); //will be 1 if transitionObject is not @Idempotent
		
		String delay = getDelay(transitionObject);
		
		return performIdempotentTransition(transitionObject, maxAttempts, delay, ex);
	}
	
	/**
	 * Determines if the <tt>Throwable</tt> is an instance of one of the classes in the 
	 * <code>exceptionsThatCauseRetry</code> array.
	 * 
	 * @param exceptionsThatCauseRetry the array of exceptions that can cause an idempotent transition to be retried
	 * @param t either a ProcessStateManagementException or the cause of a TransitionException
	 * @return true if the array is empty or if the t is an instance of a class in the array, otherwise false
	 */
	private boolean isRetriableException(Class<?>[] exceptionsThatCauseRetry, Throwable t){
		
		if( exceptionsThatCauseRetry.length == 0 )
			return true;
		
		for( Class<?> clazz : exceptionsThatCauseRetry )
			if( clazz.isInstance(t) )
				return true;
		
		return false;
	}
	
	/**
	 * Retrieves the <code>ex</code> attribute from the <tt>@Idempotent</tt>
	 * annotation of a <tt>Transition</tt> or <tt>TransitionWithResult</tt>.
	 * <p>
	 * If the transition object has not been annotated <tt>@Idempotent</tt>, then this
	 * method returns an array of type <tt>Class</tt> with length zero.
	 * 
	 * @param transitionObject an instance of <tt>Transition</tt> or <tt>TransitionWithResult</tt>
	 * @return the <code>ex</code> attribute of the <tt>@Idempotent</tt> annotation or an empty array if it is not present
	 * @throws IllegalArgumentException if the <code>transitionObject</code> is not of type <tt>Transition</tt> or <tt>TransitionWithResult</tt>
	 */
	private Class<?>[] getExceptionsThatCauseRetry(Object transitionObject) {
		
		try{
			Idempotent idempotent = transitionObject.getClass()
				.getMethod("doTransition", new Class[0]).getAnnotation(Idempotent.class);
			
			return idempotent == null ? new Class[0] : idempotent.ex();
		}
		catch(NoSuchMethodException e){
			throw new IllegalArgumentException("transitionObject is not of type Transition or TransitionWithResult", e);
		}
	}
	
	/**
	 * Retrieves the <code>maxAttempts</code> attribute from the <tt>@Idempotent</tt> 
	 * annotation of a <tt>Transition</tt> or <tt>TransitionWithResult</tt>.
	 * <p>
	 * If the transition object has not been annotated <tt>@Idempotent</tt>, then this method returns the 
	 * value <code>1</code> meaning that the transition should only be attempted once.
	 * 
	 * @param transitionObject an instance of <tt>Transition</tt> or <tt>TransitionWithResult</tt>
	 * @return the maxAttempts attribute of the <tt>@Idempotent</tt> annotation or <code>1</code> if it is not present
	 * @throws IllegalArgumentException if the <code>transitionObject</code> is not of type <tt>Transition</tt> or <tt>TransitionWithResult</tt>
	 */
	private int getMaxRetryAttempts(Object transitionObject) {
		
		try{
			Idempotent idempotent = transitionObject.getClass()
				.getMethod("doTransition", new Class[0]).getAnnotation(Idempotent.class);
			
			return idempotent == null ? 1 : idempotent.attempts();
		}
		catch(NoSuchMethodException e){
			throw new IllegalArgumentException("transitionObject is not of type Transition or TransitionWithResult", e);
		}
	}
	
	/**
	 * Validates that <code>maxAttempts</code> is greater than or equal to one.
	 * 
	 * @param maxAttempts the value to validate
	 * @throws IllegalArgumentException if <code>maxAttempts</code> is less than 1
	 */
	private void validateMaxAttempts(int maxAttempts) {
		
		if( maxAttempts < 1 )
			throw new IllegalArgumentException("maxAttempts must be greater than zero");
	}
	
	/**
	 * Validates that the <code>exceptions</code> array is not null and that none of the its elements are null.
	 * 
	 * @param exceptions the array to validate
	 * @throws IllegalArgumentException if the argument is null or if any elements in the array are null
	 */
	private void validateExceptionsThatCauseRetryArray(Class<?>[] exceptions) {
		
		if( exceptions == null )
			throw new IllegalArgumentException("Array of exception classes is null");
		
		for( Class<?> clazz : exceptions )
			if( clazz == null )
				throw new IllegalArgumentException("Element in array of exception classes is null");
	}
	
	/**
	 * Retrieves the <code>delay</code> attribute from the <tt>@Idempotent</tt> annotation of a <tt>Transition</tt> 
	 * or <tt>TransitionWithResult</tt> object.
	 * <p>
	 * If the transition object has not been annotated <tt>@Idempotent</tt>, then this method returns null.
	 * 
	 * @param transitionObject an instance of <tt>Transition</tt> or <tt>TransitionWithResult</tt>
	 * @return the delay attribute of the <tt>@Idempotent</tt> annotation or null if it is not present
	 * @throws IllegalArgumentException if the <code>transitionObject</code> is not of type <tt>Transition</tt> or <tt>TransitionWithResult</tt>
	 */
	private String getDelay(Object transitionObject){
		
		try{
			Idempotent idempotent = transitionObject.getClass()
				.getMethod("doTransition", new Class[0]).getAnnotation(Idempotent.class);
			
			return idempotent == null ? null : idempotent.delay();
		}
		catch(NoSuchMethodException e){
			throw new IllegalArgumentException("transitionObject is not of type Transition or TransitionWithResult", e);
		}
	}
	
	/**
	 * Converts the <code>delay</code> string into seconds as a numeric value.
	 * <p>
	 * The <code>delay</code> string should be a number followed by either "s", "m" or "h"
	 * to indicate wheteher the number represents seconds, minutes or hours.
	 * <p>
	 * If the delay string is "5m", for example, then this function will return (5 * 60).
	 * 
	 * @param delay the amount of delay as a string postfixed with a time unit (either "s", "m" or "h")
	 * @return the amount of delay time in seconds, or null if the delay argument is null
	 * @throws IllegalArgumentException if the delay string is empty, incorrectly formatted or if it
	 * represents a value less than zero
	 */
	private long getDelay(String delay) {
				
		if( delay == null )
			return 0;
		
		char timeUnit;
		long time;
		
		delay = delay.trim();
		
		if( "".equals(delay) )
			throw new IllegalArgumentException("Invalid delay time [" + delay + "] specified for idempotent operation");
		
		try{
			time = Integer.parseInt(delay.substring(0, delay.length() - 1));
			timeUnit = delay.charAt(delay.length() - 1);
		}
		catch(NumberFormatException e){
			throw new IllegalArgumentException("Invalid delay time [" + delay + "] specified for idempotent operation", e);
		}
		catch(IndexOutOfBoundsException e){
			throw new IllegalArgumentException("Invalid delay time [" + delay + "] specified for idempotent operation", e);
		}
		
		if( time < 0 )
			throw new IllegalArgumentException("Delay must be greater than or equal to zero");
		
		switch( timeUnit ){
		
		case 's' : return time;
		
		case 'm' : return time * 60L;
		
		case 'h' : return time * 3600L;
		
		default :
			throw new IllegalArgumentException("Invalid time unit specified. Time unit must be either 's', 'm' or 'h'");
		}
	}
	
}
